﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using WSManager.Model;

namespace WSManager.DataAccess
{
    public abstract class WebRepository
    {
        protected IEnumerable<Web> webs;
        public IEnumerable<Web> GetWebs()
        {
            return webs;
        }
        public Web GetWeb(string name)
        {
            return webs.SingleOrDefault(w => w.Name == name);
        }

        public abstract void SaveWeb(Web s);

        public abstract IEnumerable<Article> LoadArticles(string webName, IEnumerable<Category> allCategories = null);
        public abstract IEnumerable<Category> LoadCategories(string webName);
        public abstract IEnumerable<WebImage> LoadImages(string webName);
        public abstract IEnumerable<WebFile> LoadFiles(string webName);

        public virtual Web LoadWeb(string webName)
        {
            var cat = LoadCategories(webName);
            return new Web()
            {
                Name = webName,
                Articles = LoadArticles(webName, cat),
                Categories = cat,
                Images = LoadImages(webName),
                Files = LoadFiles(webName)
            };
        }

        public abstract void AddWeb(Web web);

        public abstract void Remove(string p);
    }

    public class FilesystemWebRepository : WebRepository
    {
        const string WebDataFile = "data.xml.gz";

        DirectoryInfo _directory;
        protected DirectoryInfo Directory
        {
            get
            {
                return _directory;
            }
            set
            {
                if (!value.Exists)
                {
                    value.Create();
                }
                _directory = value;
            }
        }

        public FilesystemWebRepository(string folderName = "Databases") : this(new DirectoryInfo(folderName)) { }

        public FilesystemWebRepository(DirectoryInfo dir)
        {

            this.Directory = dir;
            LoadWebs();
        }

        protected void LoadWebs()
        {

            webs = from d in Directory.GetDirectories()
                   select LoadWeb(d.Name);
        }

        protected Stream OpenWebFile(string webName, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate)
        {
            var file = new FileInfo(GetWebDataFilePath(webName));

            if (access == FileAccess.Read)
            {
                if (!file.Exists)
                {
                    AddWeb(new Web { Name = webName }.Repair());
                }
                return new GZipStream(file.Open(mode), CompressionMode.Decompress, false);
            }
            else if (access == FileAccess.Write)
            {
                return new GZipStream(file.Open(mode), CompressionMode.Compress, false);
            }
            else throw new NotSupportedException("both reading and writing is not supported");
        }

        protected string GetWebDataFilePath(string name)
        {
            return Path.Combine(Directory.FullName, name, WebDataFile);
        }

        public override IEnumerable<Article> LoadArticles(string webName, IEnumerable<Category> allCategories = null)
        {
            if (allCategories == null) allCategories = LoadCategories(webName);

            using (var stream = OpenWebFile(webName))
            {
                return
                    XDocument.Load(stream)
                        .Root.Elements("article")
                        .Select(el => ModelXmlSerializer.ToArticle(el, allCategories));
            }
        }

        public override IEnumerable<Category> LoadCategories(string webName)
        {
            using (var stream = OpenWebFile(webName))
            {
                return XDocument.Load(stream).Root.Elements("category").Select(ModelXmlSerializer.ToCategory);
            }
        }

        public override IEnumerable<WebFile> LoadFiles(string webName)
        {
            using (var stream = OpenWebFile(webName))
            {
                return
                    XDocument.Load(stream).Root.Elements("file").Select(ModelXmlSerializer.ToWebFile);
            }
        }

        public override IEnumerable<WebImage> LoadImages(string webName)
        {
            using (var stream = OpenWebFile(webName))
            {
                return XDocument.Load(stream).Root.Elements("image").Select(ModelXmlSerializer.ToWebImage);
            }
        }

        public override Web LoadWeb(string webName)
        {
            XElement xml;
            using (var stream = OpenWebFile(webName))
            {
                xml = XDocument.Load(stream).Root;
            }
            var w = ModelXmlSerializer.ToWeb(xml);
            w.Name = webName;
            return w.Repair();
        }

        public override void AddWeb(Web web)
        {
            DirectoryInfo dir = new DirectoryInfo(Path.Combine(Directory.FullName, web.Name));
            if (!dir.Exists)
            {
                dir.Create();
                dir.Refresh();
            }
            SaveWeb(web);
            LoadWebs();
        }

        public override void SaveWeb(Web s)
        {
            if (s == null) throw new ArgumentNullException("s");

            using (var stream = OpenWebFile(s.Name, FileAccess.Write))
            {
                ModelXmlSerializer.ToXml(s).Save(stream);
            }
        }

        public override void Remove(string webName)
        {
            var dir = this._directory.GetDirectories().FirstOrDefault(d => String.Equals(d.Name, webName, StringComparison.CurrentCultureIgnoreCase));
            if (dir == null) return;
            dir.Delete(true);
            LoadWebs();
        }
    }
}